/**
 * vish.c - A shell developed for usp-osdev project
 *
 * Copyright (c) 2009 Gabriel Garcia Bim <gabrielbim@grad.usp.br>
 *                    Leandro de Goes Proença Jr. <emaildomalhado@gmail.com>
 * Date: Apr, 2009
 *
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <sys/wait.h>

#include "defs.h"
#include "builtins.h"

/*Global variable*/

extern char **environ;
pid_t child, pipe_pid;
char jobname[MAX_JOBS][1024] ;
int jobid[MAX_JOBS];
int statusjob[MAX_JOBS]; // 0 = running, 1 = stopped
int controlejob = 0; // total number of jobs
void catch1(int signum);

/*This function print the jobs list*/
void printjob(char vetor[MAX_JOBS][1024], int *index,int controle){
	int i;
	if (controle!=0){	
		printf("xxxxx= Jobs =xxxxx\n");
		for(i=0;i<controle;i++) {
			printf("Job name: %s, JobPID: %d, Status: ", vetor[i], index[i]);
			if (statusjob[i]==0)
				printf("Running\n");
			else if (statusjob[i] == 1)
				printf("Stopped\n");
		}
	}
	else printf("xxx= No Jobs =xxx\n");
}

/*This function upadte the jobs status*/
void updatejob(int index,int status){
	int i;
	for (i=0;i<controlejob;i++){
		if (jobid[i]==index)
			break;			
	}
	statusjob[i]=status;
}

/*this function delete a job from the jobs list*/
void excluijob(int index){
	int i;
	for (i=0;i<controlejob;i++) {
		if (jobid[i]==index)
			break;		
	}
	if (i==controlejob)
		controlejob--;
	else {	
		strcpy(jobname[i],jobname[controlejob]);
		jobid[i]=jobid[controlejob];
		statusjob[i]= statusjob[controlejob];	
		controlejob--;	
	}
}

/*this is a function that handle the signal SIGSTP and SIGINT*/
void catch1(int signum){  
	if (signum == SIGTSTP) {
		updatejob(child,1);
		if (child!=0) {
			kill(child,SIGTSTP);
			child = 0;
		}
	 }

	else if (signum == SIGINT) {
		if (child != 0) {
			kill(child,SIGKILL);
			child = 0;
		}
	}
	fflush(stdout);
}
/*This is the parser that transform the input in a argument vector */
void parser(char* linha_entrada, char*** args, int* no_args) {
	(*no_args) = 0;
	char argumento[MAX_CHAR_ARG]; // Buffer
	int i = 0; // Contador para leitura de linha_entrada
	int x;	// Contador para escrita no buffer do argumento

	/* Contagem do número de argumentos */
	while(linha_entrada[i] == ' ') // Desconsidera espaços adicionais
		i++;
	do {
		/* Se o caracter lido de linha_entrada não é áspas */
		if (linha_entrada[i] != '\"') {
			while((linha_entrada[i] != ' ') && (linha_entrada[i] != '\0'))
				i++;
		}
		/* Se o caracter lido for áspas */
		else {
			i++; // Desconsidera as áspas de abertura
			while((linha_entrada[i] != '\"') && (linha_entrada[i] != '\0')) {
				i++;
			}
			i++; // Desconsidera as áspas de fechamento
		}
		while (linha_entrada[i] == ' ') // Desconsidera espaços entre argumentos
			i++;
		(*no_args)++;
	} while (linha_entrada[i] != '\0');

	/* Alocação de espaço para o número de argumentos contados */
	(*args) = malloc((*no_args)*(sizeof(char*)));

	/* Armazenamento de cada argumento em (*args) */
	int arg_atual = 0;
	i = 0;
	do {
		x = 0;
		/* Se o caracter lido de linha_entrada não é áspas */
		if (linha_entrada[i] != '\"') {
			/* Copia string para o buffer do argumento até <espaço> ou o fim da string */
			while((linha_entrada[i] != ' ') && (linha_entrada[i] != '\0')) {
				argumento[x] = linha_entrada[i];
				i++;
				x++;
			}
		}
		/* Se o caracter lido for áspas */
		else {
			i++; // Desconsidera as áspas de abertura
			/* Copia string para o buffer do argumento até as áspas de fechamento ou o fim da string */
			while((linha_entrada[i] != '\"') && (linha_entrada[i] != '\0')) {
				argumento[x] = linha_entrada[i];
				i++;
				x++;
			}
			i++; // Desconsidera as áspas de fechamento
		}
		argumento[x] = '\0';
		while (linha_entrada[i] == ' ') // Desconsidera espaços entre argumentos
			i++;
		(*args)[arg_atual] = malloc(MAX_CHAR_ARG*sizeof(char)); //Aloca espaço para o argumento no vetor
		strcpy((*args)[arg_atual],argumento);
		arg_atual++;
	} while (linha_entrada[i] != '\0');
}

/* Main Function*/
int main(int argc, char **argv) {
	int status;
	int fd;
	
	char *line;
	char comando[1024];
	size_t len = 0;
	char redir_saida[100], redir_entrada[100], redir_saida2[100], redir_entrada2[100];
	int pfd[2]; // Pipe file descriptor
	int pipe_present;
	int fd_monitor = 100; // Armazena a saída do monitor em um file descriptor
	dup2(STDOUT_FILENO, fd_monitor); // para poder voltar quando ocorre um pipe
	child = 0;
	int i;

	signal(SIGTSTP, catch1);
	signal(SIGINT, catch1);

	while (1) {
		comando[0] = '\0';
		redir_saida[0] = '\0';
		redir_entrada[0] = '\0';
		redir_saida2[0] = '\0';
		redir_entrada2[0] = '\0';
		pipe_present = 0;

		printf(PROMPT);
		fflush(stdout);
		
		getline(&line,&len,stdin);
		
		line[strlen(line) - 1] = '\0';

		char** vetor_args;
		int n_args;
		parser(line, &vetor_args, &n_args);
		
		/* Encontra redirecionamentos de saída/entrada */
		for(i=0; i<n_args;i++) {
			if (strcmp(vetor_args[i], ">") == 0) {
				strcpy(redir_saida, vetor_args[i+1]);
			}
		}
		for (i=0; i<n_args;i++) {
			if (strcmp(vetor_args[i], "<") == 0) {
				strcpy(redir_entrada, vetor_args[i+1]);
			}
		}
		for (i=0; i<n_args; i++) {
			if (strcmp(vetor_args[i], ">>") == 0) {
				strcpy(redir_saida2, vetor_args[i+1]);
			}
		}
		for (i=0; i<n_args; i++) {
			if (strcmp(vetor_args[i], "<<") == 0) {
				strcpy(redir_entrada2, vetor_args[i+1]);
			}
		}
		for (i=0;i<n_args;i++) {
			if (strcmp(vetor_args[i], "|") == 0) {
				pipe_present = 1;
				pipe(pfd);
				if ((pipe_pid = fork()) > 0) { // Parent (writer)
					close(pfd[0]); // Leitura do pipe não usada
					close(STDOUT_FILENO); // Redireciona a saída para o pipe
					dup(pfd[1]);
					close(pfd[1]);
					vetor_args[i] = NULL;
					n_args = i;
				}

				else { // Child (reader)
					close(pfd[1]); // Escrita do pipe não usada
					close(STDIN_FILENO); // Redireciona a entrada para o pipe
					dup(pfd[0]);
					close(pfd[0]);
					int j;
					for (j = i; j < n_args; j++) {
						vetor_args[j-i] = vetor_args[j+1]; // Transfere os argumentos depois do "|" para o início do vetor
					}
					n_args = n_args - i - 1;
					vetor_args[n_args] = NULL;
				}
			}
		}

		/* Constroi o vetor de argumentos para execução */
		char** vetor_args_exec = malloc(n_args*(sizeof(char*)));

		i = 0;
		while ( (i < n_args) && 
		(strcmp(vetor_args[i], ">")) &&
		(strcmp(vetor_args[i], "<")) &&
		(strcmp(vetor_args[i], "|")) &&
		(strcmp(vetor_args[i], ">>")) ) {
			vetor_args_exec[i] = vetor_args[i];
			i++;
		}
		vetor_args_exec[i] = NULL;
		
		/* Comandos internos executados pelo pai */
		strcpy(comando, vetor_args[0]);

		if (strcmp(comando, "exit") == 0){
			for(i=0;i<=controlejob;i++)
				 kill(jobid[i],SIGKILL);   
			cm_exit();
		}
		else if (strcmp(comando, "jobs") == 0) {
			printjob(jobname,jobid,controlejob);
		}
		else if (strcmp(comando, "bg") == 0){
			kill(jobid[controlejob-1],SIGCONT);
			updatejob(jobid[controlejob-1],0);			
		}
		
		else if (strcmp(comando, "fg") == 0){
			child = jobid[controlejob-1];
			kill(child,SIGCONT);
			updatejob(child,0);
			waitpid(child,&status,WUNTRACED);
			
			if (WIFSTOPPED(status)){ // Se o filho parou mas não terminou
				updatejob(child,1);
			}
			if (WIFEXITED(status)){ // Se o filho terminou normalmente
				excluijob(child);
			}
			if (WIFSIGNALED(status)){ // Se o filho terminou com sinal
				excluijob(child);
			}

			child = 0; 
		}

		else if (strcmp(comando, "cd") == 0) {
			cm_cd(vetor_args[1]);
		}

		/* Task executed by son */
		else {strcpy(jobname[controlejob],comando);
			if ((child = fork()) != 0) {
				jobid[controlejob]=child;
				statusjob[controlejob]=0;
				controlejob++;
				waitpid(child,&status,WUNTRACED);
				
				// test status
				if (WIFSTOPPED(status)){ // Se o filho parou mas não terminou
					updatejob(child,1);
				}
				if (WIFEXITED(status)){ // Se o filho terminou normalmente
					excluijob(child);
				}
				if (WIFSIGNALED(status)){ // Se o filho terminou com sinal
					excluijob(child);
				}
				child = 0; // Porque não há mais filho em foreground
			}
			else {
				if (strcmp(redir_saida, "") != 0) { // Caso a saída deva ser redirecionada para redir_saida
					if ((fd = open(redir_saida, O_CREAT | O_TRUNC | O_WRONLY, S_IWUSR | S_IRUSR)) <0) {
						printf("Not possible to open/create file %s\n", redir_saida);
						exit(0);
					}
					else {
						/* Redirecionando a saída */
						close(STDOUT_FILENO);
						dup(fd);
						close(fd);
					}
				}
				if (strcmp(redir_saida2, "") != 0) { // Caso a saída deva ser redirecionada para redir_saida2 com append
					if ((fd = open(redir_saida2, O_CREAT | O_APPEND | O_WRONLY , S_IWUSR | S_IRUSR)) <0) {
						printf("Not possible to open/create file %s\n", redir_saida2);
						exit(0);
					}
					else {
						/* Redirecionando a saída */
						close(STDOUT_FILENO);
						dup(fd);
						close(fd);
					}
				}
				if (strcmp(redir_entrada, "") != 0) { // Caso a entrada deva ser redirecionada para redir_entrada
					if ((fd = open(redir_entrada, O_RDONLY, S_IWUSR | S_IRUSR)) < 0) {
						printf("Erro ao abrir o arquivo %s\n", redir_entrada);
						exit(0);
					}
					else {
						/* Redirecionamento da entrada */
						close(STDIN_FILENO);
						dup(fd);
						close(fd);
					}
				}
				if (strcmp(redir_entrada2, "") != 0) { // Caso de a entrada ser lida até encontrar redir_entrada2
					//TODO: Redirecionamento <<
					printf("Ainda não implementado: realizar redirecionamento do tipo <<\n");
				}

				/* Intern task that is executed by son */
				if (strcmp(comando, "help") == 0) {
					cm_help();
				}

				else {
					/* try to executed directly */
					signal(SIGINT, SIG_IGN); // Para o filhho ignorar o sinal SIGINT
					execve(comando, vetor_args, environ);

					/* Try to executed by PATH */
					char copia_path[1024];
					char path_exec[1024];
					char *path;

					strcpy(copia_path,getenv("PATH")); // Copia o PATH para não ser alterado pelo strtok
					path = strtok(copia_path,":");
					while(path != NULL) {
						strcpy(path_exec, path);
						strcat(path_exec, "/");
						strcat(path_exec, comando);
						signal(SIGINT, SIG_IGN); // Para o filhho ignorar o sinal SIGINT
						execve(path_exec, vetor_args_exec, environ);
						path = strtok(NULL, ":");
					}
					printf("Error, command not found: %s\n", comando);
					exit(0);
				}
			}
		}

		if (pipe_present) { // Son end if the pipe existed
			if (pipe_pid == 0)
				exit(0);
			else {
				close(STDOUT_FILENO);
				dup(fd_monitor);
				waitpid(pipe_pid, &status, 0);
			}
		}
	}
	return(0);
}
